package wator.model;

import java.util.Observable;

import wator.model.topologies.TorusTopology;
import wator.util.GlobalConfiguration;

/**
 * This class represents a simulation and all its necessary parameters. The 
 * most important functionality encapsulated in this class is the ability to 
 * iterate the system. Since we do not support multiple parallel simulations,
 * this class is implemented as singleton pattern.
 * 
 * @see #iterate(int)
 */
public class Simulation extends Observable {
	
	private static Simulation singleton = new Simulation();
	//private final static Logger log = Logger.getLogger("Simulation");
	
	private Topology topo;
	private Population pop;
	
	/**
	 * Get the singleton instance of <code>Simulation</code>. This is no
	 * lazy singleton pattern implementation, so the singleton is created at
	 * program start in a static way.
	 *  
	 * @return the singleton instance of <code>Simulation</code>
	 */
	public static Simulation getInstance() {
		return singleton;
	}
	
	/**
	 * Initializes the simulation. This should be called at the begin of the
	 * program before the UI is initialized. The simulation is initialized with
	 * an empty population and a torus topology in a default sized universe
	 * with dimension 2.
	 */
	public void initialize() {
		topo = new TorusTopology();
		pop = new Population(topo);
		setSizeVector(IntegerVector.getDefaultVector(Constants.DEFAULT_DIM));
	}
	
	/**
	 * Iterates the system. Gives all agents the possibility to eat, move, 
	 * breed, and die :) Mediate decisions made by the individuals to 
	 * neighbors and manages synchronization with the population data set.
	 * 
	 * @param n number of iterations which should be done at once
	 * @throws NullPointerException if the simulation was not initialized
	 * 			by {@link #initialize()}
	 * @see Population#iterate()
	 */
	public void iterate(int n) {
		
		for (int i = 0; i < n; i++) {
			if(GlobalConfiguration.debug)
				System.out.println("Iterate : "+i);
			pop.iterate();
			populationChanged();
		}
	}
	
	/**
	 * Set the used topology. If the size vector of the new topology differs 
	 * from the current simulation dimension, call 
	 * {@link #setSizeVector(IntegerVector)} too.
	 * 
	 * @param topo the new topology
	 * @throws NullPointerException if <code>topo</code> is null
	 */
	public void setTopology(Topology topo) throws NullPointerException {
		if (topo == null) {
			throw new NullPointerException();
		}
		this.topo = topo;
		pop.setTopology(topo);
		if (topo.getSizeVector() != null) {
			setSizeVector(topo.getSizeVector());
		}
	}
	
	/**
	 * Get the used topology. Note that setting the size vector should
	 * be done indirectly via {@link #setSizeVector(IntegerVector)} to assure
	 * that no inconsistencies arise between the population and the
	 * topology.
	 * 
	 * @return the used topology
	 */
	public Topology getTopology() {
		return topo;
	}
	
	/**
	 * Get the current population. Note that setting the size vector should
	 * be done indirectly via {@link #setSizeVector(IntegerVector)} to assure
	 * that no inconsistencies arise between the population and the
	 * topology.
	 * 
	 * @return the current population
	 */
	public Population getPopulation() {
		return pop;
	}
	
	/**
	 * Set the current simulation size vector and propagate it to the
	 * population and the topology.
	 * 
	 * @param sizeVector the new size vector
	 * @see Topology#setSizeVector(IntegerVector)
	 * @see Population#setSizeVector(IntegerVector)
	 */
	public void setSizeVector(IntegerVector sizeVector) {
		topo.setSizeVector(sizeVector);
		pop.setSizeVector(sizeVector);
		sizeVectorChanged();
	}
	
	/**
	 * Get the current simulation size vector. This is equivalent to
	 * getting it from the current topology or population.
	 * 
	 * @return current size vector
	 */
	public IntegerVector getSizeVector() {
		return topo.getSizeVector();
	}
	
	
	/* (non-Javadoc)
	 * Notifies observers that the population has changed.
	 */
	private void populationChanged() {
		this.setChanged();
		this.notifyObservers(pop);
	}
	
	/* (non-Javadoc)
	 * Notifies observers that the size vector has changed.
	 */
	private void sizeVectorChanged() {
		this.setChanged();
		this.notifyObservers(topo.getSizeVector());
	}
}
